import { BigNumber, ethers, utils } from "ethers";
import abi from "./abi.json";

/**
 * @description web3通用封装类
 * @author lecoler
 * @date 2023/2/10
 * @install npm install -S ethers
 * @example
 *          const app = new App();
 *          app.init(),then(console.log)
 *          app.getWalletAddress().then(console.log)
 */

class App {
  constructor() {
    this.Provider = null;
    this.Signer = null;
  }

  /**
   * @description 初始化web3，完成返回Promise<true>,失败Promise<false>
   * @author lecoler
   * @date 2023/2/9
   * @return Promise<boolean>
   */
  async init() {
    try {
      this.Provider = new ethers.providers.Web3Provider(window.ethereum);
      this.Signer = this.Provider.getSigner();
      await this.getNetwork();
      return true;
    } catch (e) {
      console.log(e, "e");
      return false;
    }
  }

  /**
   * @description 实例化合约
   * @author lecoler
   * @date 2023/2/9
   * @param {String} contractAdr - 合约地址
   * @return Contract
   */
  initContract(contractAdr) {
    return new ethers.Contract(contractAdr, abi, this.Signer);
  }

  /**
   * @description 获取网络信息
   * @author lecoler
   * @date 2023/2/9
   * @return Promise<object>
   */
  async getNetwork() {
    return await this.Provider.getNetwork();
  }

  /**
   * @description 获取当前钱包地址
   * @author lecoler
   * @date 2023/2/9
   * @return Promise<string>
   */
  async getWalletAddress() {
    return (await this.Provider.send("eth_requestAccounts", []))[0];
  }

  /**
   * @description 检查是否有效地址，有效则返回地址字符，无效则null
   * @author lecoler
   * @date 2023/2/9
   * @param {String} address - 待检查地址
   * @return String|null
   */
  checkAddress(address) {
    try {
      return utils.getAddress(address);
    } catch (e) {
      console.log("地址不正确");
      return null;
    }
  }

  /**
   * @description 签名
   * @author lecoler
   * @date 2022/11/7
   * @param {String|Number} num - 随机数
   * @return Promise<String>
   */
  async signature(num) {
    return this.Signer.signMessage(`${num}`);
  }

  /**
   * @description 授权代币
   * @author lecoler
   * @date 2022/10/22
   * @param {String} contractAdr - 合约地址
   * @param {String} toAdr - 要授权到的地址
   * @param {String|Number} num - 授权数量
   * @return Promise
   */
  async authBalance(contractAdr, toAdr, num) {
    try {
      const contract = new ethers.Contract(contractAdr, abi, this.Signer);
      const res = await contract.approve(toAdr, BigNumber.from(num));
      return await res.wait();
    } catch (e) {
      console.log(e);
    }
  }

  /**
   * @description 获取已授权的代币数量
   * @author lecoler
   * @date 2022/10/22
   * @param {String} contractAdr - 合约地址
   * @param {String} authAdr - 已给授权的地址
   * @param {String} [walletAddress] - 钱包地址，无则默认用当前钱包
   * @return Promise<BigNumber>
   */
  async checkAuthBalance(contractAdr, authAdr, walletAddress) {
    const contract = new ethers.Contract(contractAdr, abi, this.Signer);
    return await contract.allowance(walletAddress, authAdr);
  }

  /**
   * @description 获取合约信息
   * @author lecoler
   * @date 2022/10/22
   * @param {String} contractAdr - 合约地址
   * @return Promise<{name,symbol,decimals}>
   */
  async getContractInfo(contractAdr) {
    const contract = new ethers.Contract(contractAdr, abi, this.Signer);
    const name = await contract.name();
    const decimals = await contract.decimals();
    const symbol = await contract.symbol();
    return {
      name,
      symbol,
      decimals,
    };
  }

  /**
   * @description 获取Gas Price
   * @author lecoler
   * @date 2022/9/28
   * @return Promise<{BigNumber}>
   */
  async getGasPrice() {
    return await this.Provider.getFeeData();
  }

  /**
   * @description 获取转账的gasLimit
   * @author lecoler
   * @date 2022/11/8
   * @param {String} contractAdr - 合约地址
   * @param {String} collectionAdr - 接收地址
   * @param {String|Number} [num] - 数量，1111.12
   * @return Promise
   */
  async getGasLimit(contractAdr, collectionAdr, num) {
    if (contractAdr) {
      const contract = new ethers.Contract(contractAdr, abi, this.Signer);
      const decimals = await contract.decimals();
      return await contract.estimateGas.transfer(
        collectionAdr,
        ethers.utils.parseUnits(String(num), decimals)
      );
    } else {
      return await this.Provider.estimateGas({
        to: collectionAdr,
      });
    }
  }

  /**
   * @description 转账，合约地址存在是为转合约币，为null转主币
   * @author lecoler
   * @date 2022/11/8
   * @param {String} contractAdr - 合约地址
   * @param {String} collectionAdr - 接收地址
   * @param {String|Number} num - 转账数量，1124.15
   * @return Promise
   */
  async transfer(contractAdr, collectionAdr, num) {
    const gas = await this.getGasPrice();
    const gasPrice = gas.maxFeePerGas || gas.gasPrice;
    const gasLimit = await this.getGasLimit(contractAdr, collectionAdr, num);

    if (contractAdr) {
      const contract = new ethers.Contract(contractAdr, abi, this.Signer);
      const decimals = await contract.decimals();
      try {
        return await contract.transfer(
          collectionAdr,
          ethers.utils.parseUnits(String(num), decimals),
          {
            gasPrice,
            gasLimit,
          }
        );
      } catch (e) {
        return "error";
      }
    } else {
      try {
        return await this.Signer.sendTransaction({
          to: collectionAdr,
          value: ethers.utils.parseEther(String(num)),
          gasPrice,
          gasLimit,
        });
      } catch (e) {
        return "error";
      }
    }
  }

  /**
   * @description 查询钱包指定代币余额
   * @author lecoler
   * @date 2023/2/9
   * @param {String} contractAdr - 合约地址，null则为主币
   * @param {String} walletAdr - 查询的钱包地址
   * @return Promise<BigNumber>
   */
  async getBalance(contractAdr, walletAdr) {
    if (contractAdr) {
      const contract = this.initContract(contractAdr);
      const decimals = await contract.decimals();
      const num = await contract.balanceOf(walletAdr);
      return utils.formatUnits(num, decimals);
    } else {
      const num = await this.Provider.getBalance(walletAdr);
      return utils.formatEther(num);
    }
  }

  /**
   * @description 获取最新区块时间
   * @author lecoler
   * @date 2023/2/9
   * @return Promise<Number>
   */
  async getBlockTime() {
    const blockNum = await this.Provider.getBlockNumber();
    const blockInfo = await this.Provider.getBlock(blockNum);
    return blockInfo.timestamp * 1000;
  }

  /**
   * @description 启用监听区块刷新
   * @author lecoler
   * @date 2023/2/9
   * @param {Function} fun - 刷新时触发的方法
   */
  onBlock(fun) {
    this.Provider.off("block");
    this.Provider.on("block", () => fun());
  }

  /**
   * @description 启用监听账户变化
   * @author lecoler
   * @date 2023/2/9
   * @param {Function} fun - 改变时触发的方法
   */
  onAccountsChanged(fun) {
    window.ethereum.on("accountsChanged", (accounts) => {
      fun(accounts);
    });
  }

  /**
   * @description 启用监听网络变化
   * @author lecoler
   * @date 2023/2/9
   * @param {Function} fun - 改变时触发的方法
   */
  onChainChanged(fun) {
    window.ethereum.on("chainChanged", (chainId) => {
      fun(chainId);
    });
  }
}

export default App;
